﻿using System;
using System.Collections.Concurrent;
using Communication.Enums;

namespace Communication.Help.Crc;

/// <summary>
///     CRC16校验类，支持多种CRC16校验模式
/// </summary>
public class Crc16(Crc16Mode mode)
{
    /// <summary>
    ///     线程安全的字典，用于缓存不同CRC16模式对应的参数
    /// </summary>
    private static readonly ConcurrentDictionary<Crc16Mode, Lazy<Parameter>> Parameters = new();

    /// <summary>
    ///     延迟初始化当前CRC16模式对应的参数
    /// </summary>
    private readonly Lazy<Parameter> _parameter =
        Parameters.GetOrAdd(mode, static a => new Lazy<Parameter>(GetParameter(a)));

    /// <summary>
    ///     获取当前CRC16模式的配置参数
    /// </summary>
    private Parameter Crc16Config => _parameter.Value;

    /// <summary>
    ///     根据不同的CRC16模式获取对应的参数配置
    /// </summary>
    /// <param name="mode">CRC16模式</param>
    /// <returns>对应模式的参数配置</returns>
    private static Parameter GetParameter(Crc16Mode mode)
    {
        return mode switch
        {
            Crc16Mode.Ibm => new Parameter(0x8005, 0x0000, true, true, 0x0000),
            Crc16Mode.CcittFalse => new Parameter(0x1021, 0xFFFF, false, false, 0x0000),
            Crc16Mode.Modbus => new Parameter(0x8005, 0xFFFF, true, true, 0x0000),
            Crc16Mode.Usb => new Parameter(0x8005, 0xFFFF, true, true, 0xFFFF),
            Crc16Mode.Xmodem => new Parameter(0x1021, 0x0000, false, false, 0x0000),
            Crc16Mode.Dnp => new Parameter(0x3D65, 0x0000, true, true, 0xFFFF),
            Crc16Mode.Ccitt => new Parameter(0x1021, 0x0000, true, true, 0x0000),
            Crc16Mode.Maxim => new Parameter(0x8005, 0x0000, true, true, 0xFFFF),
            _ => throw new ArgumentException("Unsupported CRC16 mode")
        };
    }

    /// <summary>
    ///     计算数据的CRC16校验
    /// </summary>
    /// <param name="bytes">输入数据</param>
    /// <returns>计算得到的CRC16校验</returns>
    public ushort ComputeChecksum(ReadOnlySpan<byte> bytes)
    {
        var crc = Crc16Config.InitialValue;

        foreach (var b in bytes)
        {
            if (Crc16Config.InputReflected)
            {
                var pos = (byte)((crc & 0xFF) ^ b);
                crc = (ushort)((crc >> 8) ^ Crc16Config.Table[pos]);
            }
            else
            {
                var pos = (byte)(((crc >> 8) ^ b) & 0xFF);
                crc = (ushort)((crc << 8) ^ Crc16Config.Table[pos]);
            }
        }

        if (Crc16Config is { ResultReflected: true, InputReflected: false })
        {
            crc = Reflect16(crc);
        }

        return (ushort)(crc ^ Crc16Config.XorOut);
    }

    /// <summary>
    ///     计算字节数组的CRC16校验
    /// </summary>
    /// <param name="bytes">输入字节数组</param>
    /// <returns>计算得到的CRC16校验</returns>
    public ushort ComputeChecksum(byte[] bytes)
    {
        return ComputeChecksum(bytes.AsSpan());
    }

    /// <summary>
    ///     计算只读内存缓冲区的CRC16校验
    /// </summary>
    /// <param name="buffer">输入缓冲区</param>
    /// <returns>计算得到的CRC16校验</returns>
    public ushort ComputeChecksum(ReadOnlyMemory<byte> buffer)
    {
        return ComputeChecksum(buffer.Span);
    }

    /// <summary>
    ///     计算字节数组的CRC16校验，并返回字节数组形式的校验结果
    /// </summary>
    /// <param name="bytes">输入字节数组</param>
    /// <returns>校验的字节数组表示</returns>
    public byte[] ComputeChecksumBytes(byte[] bytes)
    {
        var crc = ComputeChecksum(bytes);
        return BitConverter.GetBytes(crc);
    }

    /// <summary>
    ///     16位数值的位反转
    /// </summary>
    /// <param name="value">要反转的16位数值</param>
    /// <returns>反转后的16位数值</returns>
    private static ushort Reflect16(ushort value)
    {
        value = (ushort)(((value & 0x5555) << 1) | ((value & 0xAAAA) >> 1));
        value = (ushort)(((value & 0x3333) << 2) | ((value & 0xCCCC) >> 2));
        value = (ushort)(((value & 0x0F0F) << 4) | ((value & 0xF0F0) >> 4));
        value = (ushort)((value << 8) | (value >> 8));
        return value;
    }

    /// <summary>
    ///     CRC16参数配置类，包含多项式、初始值、是否反转等配置信息
    /// </summary>
    private class Parameter
    {
        public Parameter(ushort polynomial, ushort initialValue, bool inputReflected, bool resultReflected,
            ushort xorOut)
        {
            Polynomial = polynomial;
            InitialValue = initialValue;
            InputReflected = inputReflected;
            ResultReflected = resultReflected;
            XorOut = xorOut;
            Table = new ushort[256];
            GenerateTable();
        }

        /// <summary>
        ///     CRC16查找表
        /// </summary>
        public ushort[] Table { get; }

        /// <summary>
        ///     CRC16多项式
        /// </summary>
        private ushort Polynomial { get; }

        /// <summary>
        ///     CRC16初始值
        /// </summary>
        public ushort InitialValue { get; }

        /// <summary>
        ///     输入是否需要反转
        /// </summary>
        public bool InputReflected { get; }

        /// <summary>
        ///     结果是否需要反转
        /// </summary>
        public bool ResultReflected { get; }

        /// <summary>
        ///     输出异或值
        /// </summary>
        public ushort XorOut { get; }

        /// <summary>
        ///     生成CRC16查找表
        /// </summary>
        private void GenerateTable()
        {
            var poly = InputReflected ? Reflect16(Polynomial) : Polynomial;

            for (var i = 0; i < 256; i++)
            {
                var crc = (ushort)(InputReflected ? i : i << 8);

                for (var j = 0; j < 8; j++)
                {
                    crc = InputReflected
                        ? (crc & 0x0001) != 0 ? (ushort)((crc >> 1) ^ poly) : (ushort)(crc >> 1)
                        : (crc & 0x8000) != 0
                            ? (ushort)((crc << 1) ^ poly)
                            : (ushort)(crc << 1);
                }

                Table[i] = crc;
            }
        }
    }
}